#include "basic.h"
#include "io.h"
#include "ping.h"
#include <pthread.h>
#include "List/simclist_impl.h"
#include "host_management.h"

#include "file_management.h"
#include "query_management.h"
#include "connections.h"
#include "boot_methods.h"

#include <math.h>

in_addr_t peer_routine(in_addr_t superpeer_ip, in_addr_t my_ip) {

	fd_set rset; //insieme dei descrittori legati alle socket sulle quali lavora la select

	int listensd, connsd, joinsock; //descrittori di : 1.socket listen 	2.socket aperto per la connessione successiva   3. socket join
	int ready, n, i;
	struct timeval timeout; //timeout per la select
	fd_set joinset; //insieme di descrittori per la select
	struct in_addr struct_superpeer_ip;
	int ping_socket;
	int superpeer_is_alive = 1;
	int maxd = 0; //valore massimo tra i descrittori nella select
	struct sockaddr_in fileaddr, connected_addr, ping_address, my_address,
			sp_address;
	char inputline[1024] = { 0 }; // lettura comandi da input
	struct_superpeer_ip.s_addr = superpeer_ip;

	list_t my_file_list; // lista file-->ip peer associati
	list_init(&my_file_list);
	list_attributes_comparator(&my_file_list, list_comparator_string);
	list_attributes_seeker(&my_file_list, string_seeker);
	list_attributes_copy(&my_file_list, list_meter_string, 1);

	/*---> * INIZIO PROCEDURA DI JOIN **/
	if ((joinsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { /* crea il socket */
		printf("errore in socket joinsock riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	memset((void *) &my_address, 0, sizeof(my_address));
	my_address.sin_family = AF_INET;
	my_address.sin_addr.s_addr = htonl(INADDR_ANY); /* il peer accetta pacchetti su una qualunque delle sue interfacce di rete */
	my_address.sin_port = htons(JOIN_LEAVE_PORT); /* numero di porta utilizzata dal peer per il Join/Leave */

	// assegna l'indirizzo al socket
	if (bind(joinsock, (struct sockaddr *) &my_address, sizeof(my_address)) < 0) {
		printf("errore in bind riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	//descrittore select per join
	FD_ZERO(&joinset);
	FD_SET(joinsock, &joinset);

	//istanzio la struttura del SP address
	memset((void *) &sp_address, 0, sizeof(sp_address));
	sp_address.sin_family = AF_INET;
	sp_address.sin_port = htons(JOIN_LEAVE_PORT); /* porta remota del SP per Join/Leave */
	sp_address.sin_addr.s_addr = superpeer_ip; /* indirizzo del SP */
	int reqType = 1;/*Tipo di richiesta: 1 = join, 0 = leave)*/
	//invio la richiesta di join
	for (i = 0; i < 3; i++) {
		if (sendto(joinsock, &reqType, sizeof(int), 0,
				(struct sockaddr *) &sp_address, sizeof(sp_address)) < 0) {
			printf("errore in sendto 3\n");
			//			return -1;
		}
		printf("inviato messaggio join\n");

		int rcv, s;
		timeout.tv_sec = 1;
		timeout.tv_usec = 500000; // 1 secondo e mezzo

		s = select(FD_SETSIZE, &joinset, NULL, NULL, &timeout);
		if (s == 0) {
			printf("scaduto timeout in join\n");
		}
		rcv = 0;
		if (FD_ISSET(joinsock, &joinset)) {
			n = recvfrom(joinsock, &rcv, sizeof(int), 0, NULL, NULL);
			if (n < 0 || rcv != 2) {
				sleep(3);
				continue;
			} else {
				printf("ricevuto ack da superpeer\n");
				break;
			}
			//su questa porta arrivano solo ack join e leave in ingresso: ignoro rcv?
		}
		printf("Tentativo di connessione %d\n", i + 1);

	}
	if (i == 3) { // rieffettua la procedura di boot, ritornando l'indirizzo del SP che non risponde
		close(joinsock);
		printf("riavvio peer tra 10 secondi\n");
		sleep(10);
		return superpeer_ip;
	}

	/*---> * FINE PROCEDURA DI JOIN **/

	printf("RICHIEDI FILE:            get_file \n"); // al peer generico
	printf("RICHIEDI LISTA SUPERPEER: get_superpeer_list \n"); // al bootstrap (torna la lista dei superpeer)
	printf("LASCIA LA RETE            leave \n"); // al superpeer

	//  SOCKET PER SPEDIRE FILE

	if ((listensd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("errore in socket listensd riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	memset((void *) &fileaddr, 0, sizeof(fileaddr));
	fileaddr.sin_family = AF_INET;
	fileaddr.sin_addr.s_addr = htonl(INADDR_ANY); // in ascolto per qualunque indirizzo alla porta SERV_PORT2
	fileaddr.sin_port = htons(FILE_PORT); // sostituire con FILE_PORT

	if ((bind(listensd, (struct sockaddr *) &fileaddr, sizeof(fileaddr))) < 0) {
		printf("errore in bind listensd riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	if (listen(listensd, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf("errore in listen su listensd riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	/* Inizializza l'array di interi client contenente i descrittori utilizzati */

	// FINE SOCKET PER SPEDIRE FILE
	// SOCKET DI PING
	if ((ping_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror(
				"errore nella creazione del socket ping_socket riavvio peer tra 10 secondi\n");
		sleep(10);
		return -1;
	}

	memset((void*) &ping_address, 0, sizeof(ping_address));
	ping_address.sin_family = AF_INET;
	ping_address.sin_port = htons(PING_PORT);
	ping_address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(ping_socket, (struct sockaddr*) &ping_address,
			sizeof(ping_address)) < 0) {
		printf("errore nella bind ping_socket riavvio peer tra 10 secondi\n");
		sleep(10);
		exit(-1);

	}
	// FINE SOCKET DI PING


	pthread_t t_update;
	update_arg_t u_arguments;
	u_arguments.is_alive = &superpeer_is_alive;
	u_arguments.is_superpeer = 0; // non e' superpeer
	u_arguments.superpeer_ip = superpeer_ip; // passo indirizzo del superpeer
	u_arguments.list = &my_file_list; // passo la lista dei miei file (il thread me l'aggiorna)

	pthread_create(&t_update, NULL, (void*) &update_file, &u_arguments); // thread che controlla gli aggiornamenti dei file condivisi


	pthread_t t_ping;
	host_mgt_struct_t ping_arguments;
	ping_arguments.socket = ping_socket;
	ping_arguments.is_alive = &superpeer_is_alive;
	ping_arguments.is_superpeer = 0; // non e' superpeer
	ping_arguments.superpeer_ip = superpeer_ip;

	pthread_create(&t_ping, NULL, (void*) &answer_ping, &ping_arguments);

	/* Inizializza a zero l'insieme dei descrittori */
	FD_ZERO(&rset);
	/* Inserisce il descrittore di ascolto */

	struct timeval timeout_select = { 5, 0 };
	timeout_select.tv_sec = 5;
	timeout_select.tv_usec = 0;

	FD_SET(listensd, &rset);
	if (listensd > maxd)
		maxd = listensd;
	// descrittore dello stdinput
	FD_SET(fileno(stdin), &rset);
	if (fileno(stdin) > maxd)
		maxd = fileno(stdin);

	fd_set mem_set;
	FD_ZERO(&mem_set);

	mem_set = rset;

	while (superpeer_is_alive == 1) {

		FD_ZERO(&rset);
		rset = mem_set;

		timeout_select.tv_sec = 2;
		timeout_select.tv_usec = 0;

		/* ready e' il numero di descrittori pronti */
		if ((ready = select(FD_SETSIZE, &rset, NULL, NULL, &timeout_select))
				< 0) { // ho solo fd_sed in lettura, terzo paremetro NULL (MM)
			printf("errore in select\n");
			continue;
		}

		if (ready == 0) {
			continue;
		}


		if (FD_ISSET(listensd, &rset)) {
			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(listensd, (struct sockaddr *) &connected_addr,
					&len)) < 0) {
				printf("errore in accept su listensd\n");
				continue;
			}
			file_arg_t arguments;
			arguments.socket = connsd;

			pthread_t t1;
			pthread_create(&t1, NULL, (void*) &send_file, (void*) &arguments);

			printf("RICHIESTO FILE DA \n", inet_ntoa(connected_addr.sin_addr));

		}

		if (FD_ISSET(fileno(stdin), &rset)) { // Se può leggere dallo stdin



			if ((fgets(inputline, 1024, stdin) == NULL)) {
				printf("Errore in fgets\n");
				continue;
			}

			if ((strncmp(inputline, "get_file", 8) == 0) && (inputline[9]
					== '\0')) {

				FD_CLR(fileno(stdin),&mem_set);

				check_sublist_struct_t arguments;
				arguments.superpeer_ip = superpeer_ip;
				arguments.mem_set = &mem_set;
				arguments.my_ip = my_ip;

				pthread_t t1;
				pthread_create(&t1, NULL, (void*) &get_file_peer, &arguments);
				continue;

			}

			else if ((strncmp(inputline, "get_superpeer_list", 18) == 0)
					&& (inputline[19] == '\0')) {
				pthread_t t1;
				pthread_create(&t1, NULL, (void*) &get_superpeer_list_thread, NULL);
			}

			else if ((strncmp(inputline, "leave", 5) == 0) && (inputline[6]
					== '\0')) {
				reqType = 0; // indica leave
				sendto(joinsock, &reqType, 0, 0,
						(struct sockaddr *) &sp_address, sizeof(sp_address));
				pthread_kill(t_update);
				pthread_kill(t_ping);
				close(ping_socket);
				close(joinsock);
				close(listensd);
				printf("--------Chiuso Programma--------\n");
				exit(0);
			}

			memset((void*) &inputline, 0, sizeof(char) * 1024);

		}

	}
	//comando kill ai thread, chiusura delle socket e ritorno
	pthread_kill(t_update);
	pthread_kill(t_ping);
	close(ping_socket);
	close(joinsock);
	close(listensd);
	sleep(5);
	return superpeer_ip;

}
